package ee.logsimulator.ui.druid;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import ee.logsimulator.Model;
import ee.logsimulator.data.Actor;
import ee.logsimulator.data.ActorEvents;
import ee.logsimulator.data.Clazz;
import ee.logsimulator.data.Event;
import ee.logsimulator.data.Spell;
import ee.logsimulator.data.Type;
import ee.logsimulator.ui.AbstractPlayer;

public class RestoDruidPlayer extends AbstractPlayer {
  private static final long serialVersionUID = 1L;

  private static final int GRID_WIDTH = 200;
  private static final int GRID_HEIGHT = 70;
  private static final List<DruidHeal> LIFEBLOOM_REFRESHERS;
  private static final List<DruidHeal> CLEARCAST_REMOVERS;
  private static final List<DruidHeal> CASTED_HEALS;

  private Map<Actor, ActorInfo> actorMap;
  private LinkedList<Event> history;
  private DruidStatistics statistics;

  static {
    List<DruidHeal> l = new ArrayList<DruidHeal>();
    l.add(DruidHeal.REGROWTH);
    l.add(DruidHeal.NOURISH);
    l.add(DruidHeal.HEALING_TOUCH);
    LIFEBLOOM_REFRESHERS = Collections.unmodifiableList(l);

    List<DruidHeal> l2 = new ArrayList<DruidHeal>();
    l2.add(DruidHeal.REGROWTH);
    l2.add(DruidHeal.HEALING_TOUCH);
    CLEARCAST_REMOVERS = Collections.unmodifiableList(l2);

    List<DruidHeal> l3 = new ArrayList<DruidHeal>();
    l3.add(DruidHeal.REGROWTH);
    l3.add(DruidHeal.HEALING_TOUCH);
    l3.add(DruidHeal.REJUVENATION);
    l3.add(DruidHeal.WILD_GROWTH);
    l3.add(DruidHeal.LIFEBLOOM);
    l3.add(DruidHeal.NOURISH);
    l3.add(DruidHeal.SWIFTMEND);
    CASTED_HEALS = Collections.unmodifiableList(l3);

  }

  public class DruidStatistics {
    protected boolean harmony = true;
    protected boolean lifebloom = true;
    protected boolean clearcasting = false;
    protected int wastedRejuv = 0;
    protected int wastedWG = 0;
    protected int wastedLB = 0;
    protected int wastedOOC = 0;
    protected int swiftmend = -1;
    protected int tol = -1;
    protected int tranq = -1;
    protected int wg = -1;
  }

  public enum DruidHeal {
    REGROWTH("Regrowth", 1500, 200),
    HEALING_TOUCH("Healing Touch", 2300, 200),
    REJUVENATION("Rejuvenation", 200, 1000),
    WILD_GROWTH("Wild Growth", 200, 1500),
    LIFEBLOOM("Lifebloom", 200, 1500),
    NOURISH("Nourish", 2300, 200),
    SWIFTMEND("Swiftmend", 200, 1500);

    private static final Map<String, DruidHeal> m;

    static {
      HashMap<String, DruidHeal> map = new HashMap<String, DruidHeal>();
      for (DruidHeal dh : values()) {
        map.put(dh.name, dh);
      }
      m = Collections.unmodifiableMap(map);
    }

    public String name;
    public int preHeal;
    public int afterHeal;

    private DruidHeal(String name, int preHeal, int afterHeal) {
      this.name = name;
      this.preHeal = preHeal;
      this.afterHeal = afterHeal;
    }

    public static DruidHeal fromSpell(String spell) {
      return m.get(spell);
    }
  }

  public RestoDruidPlayer(Model model) {
    super(model);
    history = new LinkedList<Event>();
    Set<Actor> actors = model.getTargetActors();
    actorMap = new HashMap<Actor, ActorInfo>();
    int size = 0;
    for (Actor actor : actors) {
      if (Clazz.fromName(actor.getActorClass()) != Clazz.UNKNOWN && Clazz.fromName(actor.getActorClass()) != Clazz.VEHICLE) {
        size++;
      }
    }

    int col = 0 - (int) Math.round(Math.ceil(size / 5.0 / 2.0));
    int row = 0;
    for (Actor a : actors) {
      if (Clazz.fromName(a.getActorClass()) == Clazz.UNKNOWN || Clazz.fromName(a.getActorClass()) == Clazz.VEHICLE) {
        continue;
      }
      actorMap.put(a, new ActorInfo(row, col));
      row++;
      if (row > 4) {
        row = 0;
        col++;
      }
    }
    calculateMaxHealth();
  }

  private void calculateMaxHealth() {
    resetMap();
    for (Event e : model.getEvents()) {
      Actor a = e.getTarget();
      ActorInfo info = actorMap.get(a);
      if (info == null) {
        continue;
      }
      int cur = info.getCurrentHealth();
      if (e.getType() == Type.HEAL) {
        // heal
        cur = cur + e.getAmount().getAmount();
        if (cur > info.getMaxHealth()) {
          info.increaseMaxHealth(cur - info.getMaxHealth());
        }
      } else if (e.getType() == Type.DAMAGE) {
        cur = cur - e.getAmount().getAmount();
        if (cur <= 0 && e.getAmount().getOverkill() == 0) {
          // not dead
          int diff = Math.abs(cur) + 1;
          info.increaseMaxHealth(diff);
          cur = cur + diff;
        }
        if (e.getAmount().getOverkill() > 0) {
          cur = 0;
        }
      } else if (e.getSpell().getName().equals("Rebirth") && e.getType() == null) {
        cur = info.getMaxHealth();
      }
      info.setCurrentHealth(cur);
    }
  }

  @Override
  public void paint(Graphics graphics) {
    if (scroll != null) {
      int h = scroll.getHeight();
      if (scroll.getHeight() < getHeight()) {
        h -= 24;
      }
      setSize(getWidth(), h);
    }

    if (i == null || i.getWidth(this) != getWidth() || i.getHeight(this) != getHeight()) {
      i = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
    }
    Graphics g = i.getGraphics();

    if (!pixelsCalculated) {
      fillEventPixels(g);
    }

    time = slider.getValue();
    long until = timeStart + time;
    int bottom = getHeight();
    int middle = getWidth() / 2;
    int startY = bottom - 10 - ACTOR_HEIGHT;
    long drawFrom = until - Math.round(startY / MILLIS_PER_PIXEL);
    // background
    g.setColor(Color.BLACK);
    g.fillRect(0, 0, getWidth(), getHeight());

    resetMap();
    readEvents(until);
    for (Entry<Actor, ActorInfo> e : actorMap.entrySet()) {
      drawActorInfo(g, e.getKey(), e.getValue(), middle, startY);
    }
    drawHistory(g, middle, startY);

    for (ActorEvents ae : actorEvents) {
      int pos = ae.getPosition();
      g.setColor(Color.WHITE);
      int startX = middle + (pos * (ACTOR_WIDTH + 10) + 5);
      g.drawRect(startX, startY, ACTOR_WIDTH, ACTOR_HEIGHT);

      // name
      Font font = g.getFont();
      font = font.deriveFont(24f);
      font = font.deriveFont(Font.BOLD);
      g.setFont(font);
      g.setColor(Clazz.fromName(ae.getActor().getActorClass()).getColor());
      FontMetrics metrics = g.getFontMetrics();
      String name = ae.getActor().getName();
      int nameW = metrics.stringWidth(name);
      if (nameW > ACTOR_WIDTH - 20) {
        name = shoertenSpellTo(name, metrics, ACTOR_WIDTH - 20);
        nameW = metrics.stringWidth(name);
      }
      int nameHeight = metrics.getHeight();
      g.drawString(name, startX + (ACTOR_WIDTH / 2 - nameW / 2), startY + nameHeight);

      if (time == 0) {
        continue;
      }
      // calculate and fill lists
      Map<Spell, Integer> damageDone = new HashMap<Spell, Integer>();
      List<Event> toDraw = new ArrayList<Event>();
      readEvents(until, drawFrom, ae.getEvents(), damageDone, toDraw);

      // abilities
      font = font.deriveFont(14f);
      font = font.deriveFont(Font.PLAIN);
      g.setFont(font);
      metrics = g.getFontMetrics();
      g.setColor(Color.WHITE);
      List<Entry<Spell, Integer>> spells = sortEntrySet(damageDone);
      int nr = 1;
      int spellStartX = startX + 10;
      for (Entry<Spell, Integer> e : spells) {
        int textStartY = startY + nameHeight + nr * metrics.getHeight();
        if (textStartY > bottom - 10) {
          break;
        }
        String amount = abrevateNumber(e.getValue(), 1, 3, 1);
        int len = metrics.stringWidth(amount);
        String spellName = e.getKey().getName();
        int amountStartX = startX + ACTOR_WIDTH - 10 - len;
        if (spellStartX + metrics.stringWidth(spellName) > amountStartX) {
          spellName = shoertenSpellTo(spellName, metrics, amountStartX - spellStartX);
        }
        g.drawString(spellName, spellStartX, textStartY);
        g.drawString(amount, amountStartX, textStartY);
        g.drawRect(startX + 5, textStartY - metrics.getHeight() + 4, ACTOR_WIDTH - 10, metrics.getHeight());
        nr++;
      }

    }

    graphics.drawImage(i, 0, 0, this);
  }

  private void drawHistory(Graphics g, int middle, int y) {
    FontMetrics metrics = g.getFontMetrics();
    int startX = middle - 330;
    int startY = y - 5 * GRID_HEIGHT - 20;
    int nr = 0;

    for (int i = 0; i < history.size(); i++) {
      Event e = history.get(i);
      StringBuilder sb = new StringBuilder();

      long eTime = e.getTimestamp().getTime();
      g.setColor(isTooLongIdle(i) ? Color.RED : Color.WHITE);
      Calendar cal = Calendar.getInstance();
      cal.set(2011, 1, 1, 1, 0, 0);
      cal.set(Calendar.MILLISECOND, 0);
      cal.add(Calendar.MILLISECOND, (int) (eTime - timeStart));
      SimpleDateFormat f = new SimpleDateFormat("mm:ss:SSS");
      String t = f.format(cal.getTime()) + " - ";
      g.drawString(t, startX, startY - metrics.getHeight() * nr);
      sb.append(t);

      g.setColor(Color.WHITE);
      String sp = e.getSpell().getName();
      g.drawString(sp, startX + metrics.stringWidth(sb.toString()), startY - metrics.getHeight() * nr);
      sb.append(sp);

      if (e.getAmount().getAmount() > 0) {
        g.setColor(Color.GREEN);
        g.drawString(" " + e.getAmount().toString(), startX + metrics.stringWidth(sb.toString()), startY - metrics.getHeight() * nr);
        sb.append(" " + e.getAmount().toString());
      }

      g.setColor(Color.WHITE);
      g.drawString(" => ", startX + metrics.stringWidth(sb.toString()), startY - metrics.getHeight() * nr);
      sb.append(" => ");

      g.setColor(Clazz.fromName(e.getTarget().getActorClass()).getColor());
      g.drawString(e.getTarget().getName(), startX + metrics.stringWidth(sb.toString()), startY - metrics.getHeight() * nr);
      sb.append(e.getTarget().getName());

      nr++;
    }

    nr = 0;
    startX = middle + 30;
    Font f = g.getFont();
    f = f.deriveFont(18f);
    g.setFont(f);
    int lastUsedSecAgo = 0;
    boolean onCD = false;

    g.setColor(statistics.harmony ? Color.GREEN : Color.RED);
    g.drawString("Harmony", startX, startY - metrics.getHeight() * nr);
    nr++;

    g.setColor(statistics.lifebloom ? Color.GREEN : Color.RED);
    g.drawString("Lifebloom", startX, startY - metrics.getHeight() * nr);
    nr++;

    g.setColor(statistics.clearcasting ? Color.GREEN : Color.WHITE);
    g.drawString("Clearcasting", startX, startY - metrics.getHeight() * nr);
    nr++;
    nr++;

    lastUsedSecAgo = (time - statistics.swiftmend) / 1000;
    onCD = statistics.swiftmend != -1 && lastUsedSecAgo < 15;
    g.setColor(onCD ? Color.WHITE : Color.GREEN);
    g.drawString("Swiftmend" + (onCD ? " - " + (15 - lastUsedSecAgo) : ""), startX, startY - metrics.getHeight() * nr);
    nr++;

    lastUsedSecAgo = (time - statistics.wg) / 1000;
    onCD = statistics.wg != -1 && lastUsedSecAgo < 8;
    g.setColor(onCD ? Color.WHITE : Color.GREEN);
    g.drawString("Wild Growth" + (onCD ? " - " + (8 - lastUsedSecAgo) : ""), startX, startY - metrics.getHeight() * nr);
    nr++;

    lastUsedSecAgo = (time - statistics.tranq) / 1000;
    onCD = statistics.tranq != -1 && lastUsedSecAgo < 180;
    g.setColor(onCD ? Color.WHITE : Color.GREEN);
    g.drawString("Tranquility" + (onCD ? " - " + (180 - lastUsedSecAgo) : ""), startX, startY - metrics.getHeight() * nr);
    nr++;

    lastUsedSecAgo = (time - statistics.tol) / 1000;
    onCD = statistics.tol != -1 && lastUsedSecAgo < 180;
    g.setColor(onCD ? Color.WHITE : Color.GREEN);
    g.drawString("Tree of Life" + (onCD ? " - " + (180 - lastUsedSecAgo) : ""), startX, startY - metrics.getHeight() * nr);
    nr++;

    nr = 0;
    startX += 150;
    g.setColor(Color.WHITE);
    g.drawString("Wasted rejuvenations: " + statistics.wastedRejuv, startX, startY - metrics.getHeight() * nr);
    nr++;

    g.setColor(Color.WHITE);
    g.drawString("Wasted wild growths: " + statistics.wastedWG, startX, startY - metrics.getHeight() * nr);
    nr++;

    g.setColor(Color.WHITE);
    g.drawString("Wasted lifeblooms: " + statistics.wastedLB, startX, startY - metrics.getHeight() * nr);
    nr++;

    g.setColor(Color.WHITE);
    g.drawString("Wasted clearcasts: " + statistics.wastedOOC, startX, startY - metrics.getHeight() * nr);
    nr++;
  }

  private boolean isTooLongIdle(int i) {
    if (i < history.size() - 1) {
      Event e = history.get(i);
      Event prev = history.get(i + 1);
      DruidHeal eHeal = DruidHeal.fromSpell(e.getSpell().getName());
      DruidHeal prevHeal = DruidHeal.fromSpell(prev.getSpell().getName());

      if (eHeal != null && prevHeal != null) {
        int acceptableDelay = 0;
        acceptableDelay += prevHeal.afterHeal;
        acceptableDelay += eHeal.preHeal;

        if (e.getTimestamp().getTime() - prev.getTimestamp().getTime() > acceptableDelay + 800) {
          return true;
        }
      }

    }
    return false;
  }

  private void resetMap() {
    statistics = new DruidStatistics();
    history.clear();
    for (ActorInfo i : actorMap.values()) {
      i.setMaxHealth(i.getMaxHealth());
      i.setLifebloom(null);
      i.setRegrowth(null);
      i.setRejuvenation(null);
      i.setWildGrowth(null);
    }
  }

  private void drawActorInfo(Graphics g, Actor actor, ActorInfo info, int middle, int startY) {
    int y = startY - info.getRow() * GRID_HEIGHT - GRID_HEIGHT - 10;

    // health
    g.setColor(Color.GREEN);
    double health = info.getCurrentHealth()
        / ((info.getCurrentHealth() > info.getMaxHealth() ? info.getCurrentHealth() : info.getMaxHealth()) * 1.0);
    g.fillRect(middle + info.getCol() * GRID_WIDTH, y, (int) (GRID_WIDTH * health), GRID_HEIGHT);

    // border
    g.setColor(Color.WHITE);
    g.drawRect(middle + info.getCol() * GRID_WIDTH, y, GRID_WIDTH, GRID_HEIGHT);

    // name
    g.setColor(Clazz.fromName(actor.getActorClass()).getColor());
    Font f = g.getFont();
    f = f.deriveFont(22f);
    f = f.deriveFont(Font.BOLD);
    g.setFont(f);
    FontMetrics metrics = g.getFontMetrics();
    g.drawString(shoertenSpellTo(actor.getName(), metrics, (int) (GRID_WIDTH * 0.9)), middle + info.getCol() * GRID_WIDTH
        + (int) (GRID_WIDTH * 0.25), y + GRID_HEIGHT - 27);

    // hots
    f = f.deriveFont(14f);
    g.setFont(f);
    drawHot(g, info.getRejuvenation(time), middle + info.getCol() * GRID_WIDTH, y);
    drawHot(g, info.getRegrowth(time), middle + info.getCol() * GRID_WIDTH, y);
    drawHot(g, info.getWildGrowth(time), middle + info.getCol() * GRID_WIDTH, y);
    drawHot(g, info.getLifebloom(time), middle + info.getCol() * GRID_WIDTH, y);
  }

  private void drawHot(Graphics g, Hot hot, int x, int y) {
    if (hot != null) {
      Color color = Color.MAGENTA;
      if (hot.getName().equals("Rejeuvenation")) {
        color = Color.MAGENTA;
      } else if (hot.getName().equals("Lifebloom")) {
        color = Color.RED;
      } else if (hot.getName().equals("Regrowth")) {
        color = Color.ORANGE;
      } else if (hot.getName().equals("Wild Growth")) {
        color = Color.BLUE;
      }
      g.setColor(color);
      g.fillRect(x + hot.getOffsetX(), y + hot.getOffsetY(), hot.getWidth(), hot.getWidth());

      g.setColor(Color.WHITE);
      String left = String.valueOf((hot.getDuration() - time + hot.getStart()) / 1000);
      g.drawString(left, x + hot.getOffsetX() + 6 / left.length(), y + hot.getOffsetY() + hot.getWidth() - 3);
    }

  }

  private void readEvents(long until) {
    long lastWastedWG = -20000;
    long clearcastingRemoved = -1;
    for (Event e : model.getEvents()) {
      long t = e.getTimestamp().getTime();
      if (t > until) {
        break;
      }
      ActorInfo info = actorMap.get(e.getTarget());
      if (info == null) {
        continue;
      }
      String sp = e.getSpell().getName();
      int width = 20;
      int botOffsetY = GRID_HEIGHT - width - 2;
      int topOffsetX = GRID_WIDTH - width - 2;
      long start = e.getTimestamp().getTime() - timeStart;

      boolean selectedPlayer = model.getSourceFilteredActors().contains(e.getSource());
      if (selectedPlayer && clearcastingRemoved > 0 && start - clearcastingRemoved > 500) {
        boolean used = false;
        for (Event he : history) {
          if (Math.abs(clearcastingRemoved - (he.getTimestamp().getTime() - timeStart)) < 500
              && CLEARCAST_REMOVERS.contains(DruidHeal.fromSpell(he.getSpell().getName()))) {
            used = true;
          }
        }
        if (!used) {
          statistics.wastedOOC++;
        }
        clearcastingRemoved = -1;
      }
      if (e.getType() == null) {
        if (e.getSpell().getName().equals("Rebirth")) {
          info.setCurrentHealth(info.getMaxHealth());
        }
      } else {
        switch (e.getType()) {
        case DAMAGE:
          info.setCurrentHealth(info.getCurrentHealth() - e.getAmount().getAmount());
          break;
        case HEAL:
          info.setCurrentHealth(info.getCurrentHealth() + e.getAmount().getAmount());
          if (selectedPlayer && "Lifebloom".equals(sp) && info.getLifebloom((int) start - 1000) == null
              && info.getLifebloom((int) start) == null) {
            info.setLifebloom(new Hot(sp, 10000, GRID_WIDTH / 2, botOffsetY, width, start));
            statistics.lifebloom = true;
          }
          if (selectedPlayer && info.getLifebloom((int) start) != null && LIFEBLOOM_REFRESHERS.contains(DruidHeal.fromSpell(sp))
              && e.getSubType() == 1) {
            info.setLifebloom(new Hot("Lifebloom", 10000, GRID_WIDTH / 2, botOffsetY, width, start));
            statistics.lifebloom = true;
          }
          if (selectedPlayer && "Swiftmend".equals(sp)) {
            statistics.swiftmend = (int) start;
          } else if (selectedPlayer && "Swiftmend".equals(sp)) {
            statistics.swiftmend = (int) start;
          }
          if (selectedPlayer && e.getSubType() == 1 && CASTED_HEALS.contains(DruidHeal.fromSpell(sp))) {
            addHistory(e);
          }
          break;
        case AURA:
          if (e.getSubType() == 1 || e.getSubType() == 6 || e.getSubType() == 3 || e.getSubType() == 2) {
            if (selectedPlayer && "Rejuvenation".equals(sp)) {
              info.setRejuvenation(e.getSubType() == 3 ? null : new Hot(sp, 12000, 2, botOffsetY, width, start));
              if (selectedPlayer && e.getSubType() != 3) {
                if (!statistics.harmony) {
                  statistics.wastedRejuv++;
                }
                addHistory(e);
              }
            } else if (selectedPlayer && "Regrowth".equals(sp)) {
              info.setRegrowth(e.getSubType() == 3 ? null : new Hot(sp, 6000, 2, 2, width, start));
            } else if (selectedPlayer && "Wild Growth".equals(sp)) {
              if (selectedPlayer && e.getSubType() != 3) {
                addHistory(e);
                statistics.wg = (int) start;
                if (!statistics.harmony && Math.abs(lastWastedWG - start) > 2000) {
                  statistics.wastedWG++;
                  lastWastedWG = start;
                }
              }
              info.setWildGrowth(e.getSubType() == 3 ? null : new Hot(sp, 7000, topOffsetX, 2, width, start));
            } else if (selectedPlayer && "Lifebloom".equals(sp)) {
              info.setLifebloom(e.getSubType() == 3 ? null : new Hot(sp, 10000, GRID_WIDTH / 2, botOffsetY, width, start));
              statistics.lifebloom = e.getSubType() != 3;
            } else if (selectedPlayer && "Harmony".equals(sp)) {
              statistics.harmony = e.getSubType() != 3;
            } else if (selectedPlayer && "Clearcasting".equals(sp)) {
              statistics.clearcasting = e.getSubType() != 3;
              if (!statistics.clearcasting) {
                clearcastingRemoved = start;
              }
            } else if (selectedPlayer && "Tree of Life".equals(sp) && e.getSubType() != 3) {
              statistics.tol = (int) start;
            }
          } else {
            System.out.println(e);
          }
          break;
        case GAIN:
          break;
        case CAST:
          if (selectedPlayer && "Lifebloom".equals(sp)) {
            addHistory(e);
            if (!statistics.harmony) {
              statistics.wastedLB++;
            }
            statistics.lifebloom = true;
            info.setLifebloom(new Hot(sp, 10000, GRID_WIDTH / 2, botOffsetY, width, start));
          } else if (selectedPlayer && "Tranquility".equals(sp) && (statistics.tranq < 0 || start - statistics.tranq > 8000)) {
            statistics.tranq = (int) start;
          }
          break;
        case DEATH:
          info.setCurrentHealth(0);
          break;
        }
      }
    }
  }

  private void addHistory(Event e) {
    history.addFirst(e);
  }
}
